package com.mnt.gui.base.buildfactory;

import java.lang.reflect.Field;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;

import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ComboBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.DatePicker;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.util.Callback;
import javafx.util.Pair;

import com.mnt.gui.base.buildfactory.annotation.FXTable;
import com.mnt.gui.base.buildfactory.annotation.FXTable.ColType;
import com.mnt.gui.base.buildfactory.cellhandle.DatePickerHandler;
import com.mnt.gui.base.buildfactory.cellhandle.EventExecutorAndNewValueHandle;
import com.mnt.gui.base.buildfactory.cellhandle.EventExecutorCheckHandle;
import com.mnt.gui.base.buildfactory.cellhandle.EventExecutorChoiceHandle;
import com.mnt.gui.base.buildfactory.cellhandle.EventExecutorHandle;
import com.mnt.gui.base.buildfactory.cellhandle.TextHandle;
import com.mnt.gui.base.convert.DateConvertFactory;
import com.mnt.gui.base.data.TableData;

/**
 * table column normal builder support column content convert (Button ,
 * TextField, ChoiceBox)
 * 
 * @author 2014-11-23 mnt.cico
 * 
 * @param <M>
 *            is table item class type (model)
 */
public class TableCellNormalBuilder<M> {

	private TableView<M> tableView;

	private ObservableList<M> tableItem;

	private Class<M> model;

	private Map<Integer, ColType> colTypeValidateMap = new HashMap<Integer, ColType>();
	private Map<Integer, ObservableList<?>> itemMap = new HashMap<Integer, ObservableList<?>>();

	TableCellNormalBuilder(TableView<M> tableView, ObservableList<M> tableItem, Class<M> model) {
		if (null == model || tableView == null) {
			throw new RuntimeException("input params not null!");
		}
		this.tableView = tableView;
		this.tableItem = tableItem;
		this.model = model;
		tableView.getStylesheets().add(getClass().getResource("css/tableColumn.css").toExternalForm());
		if(null != tableItem) {
			tableView.setItems(tableItem);
		}
	}

	/**
	 * column bind field with model Mar 27, 2014 mnt.cico
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void build() {
		Field[] fields = model.getDeclaredFields();
		List<TableColumn<M, ?>> tableColumns = tableView.getColumns();
		for (Field field : fields) {
			FXTable tableAnno = field.<FXTable> getAnnotation(FXTable.class);
			if (null != tableAnno) {
				if (tableAnno.colIndex() != -1) {
					if (tableAnno.isItem()) {
						try {
							field.setAccessible(true);
							Object obj = null;
							try {
								obj = field.get(model.newInstance());
								if (obj instanceof ObservableList<?>) {
									itemMap.put(tableAnno.colIndex(), (ObservableList<?>) obj);
								}
							} catch (InstantiationException e) {
								e.printStackTrace();
							}
						} catch (IllegalArgumentException e) {
							e.printStackTrace();
						} catch (IllegalAccessException e) {
							e.printStackTrace();
						}
					} else {
						TableColumn<M, ?> tableColumn = tableColumns.get(tableAnno.colIndex());
						if (null != tableColumn) {
							tableColumn.setCellValueFactory(new PropertyValueFactory(field.getName()));
							tableColumn.setCellFactory(param -> {
								return new TableCell() {
									protected void updateItem(final Object item, boolean empty) {
										super.updateItem(item, empty);
										setAlignment(Pos.CENTER);
										if(empty) {
											setText("");
										} else {
											if(null == item) {
												setText("");
											} else {
												setText(item.toString());
											}
										}
									}
								};
							});
							colTypeValidateMap.put(tableAnno.colIndex(), tableAnno.colType());
						}
					}
				}
			}
		}
	}

	/**
	 * table column to button and action event 2014-3-29 mnt.cico
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <E extends Event, V> void buildButtonCol(int index, final EventType<E> eventType, final EventExecutorHandle<E, M, V> executor) {
		checkColWithIndex(index, ColType.BUTTON);
		TableColumn table = tableView.getColumns().get(index);
		table.setCellFactory(new Callback<TableColumn<M, ?>, TableCell<M, ?>>() {
			@Override
			public TableCell<M, ?> call(TableColumn<M, ?> param) {
				return new TableCell() {
					private EventHandler<E> eventHandler;
					@Override
					protected void updateItem(final Object item, boolean empty) {
						if (tableView.getItems().size() > getIndex() && getIndex() >= 0) {
							final Button button;
							if(getGraphic() == null) {
								button = new Button(null == item ? "button" : item.toString());
								button.getStylesheets().add(getClass().getResource("css/tableColumn.css").toExternalForm());
								setAlignment(Pos.CENTER);
								setGraphic(button);
							} else {
								button = (Button) getGraphic();
								button.setText(null == item ? "button" : item.toString());
							}
							if(null != eventHandler) {
								button.removeEventHandler(eventType, eventHandler);
							}
							eventHandler = new EventHandler<E>() {
								@Override
								public void handle(E event) {
									try {
										button.setDisable(true);
										executor.execute(event, (M) tableView.getItems().get(getIndex()), (V) item);
									} catch (Exception e) {
										executor.onFailed(e);
									}
									button.setDisable(false);
								}
							};
							button.addEventHandler(eventType, eventHandler);
						} else {
							setGraphic(null);
						}
						super.updateItem(item, empty);
					}
				};
			}

		});
	}

	/**
	 * the class is not String or Integer.. use this function convert to String
	 * value to show. 2014-3-29 mnt.cico
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <V> void buildTextCol(int index, final TextHandle<V> valueHandle) {
		checkColWithIndex(index, ColType.TEXT);
		TableColumn table = tableView.getColumns().get(index);
		table.setCellFactory(new Callback<TableColumn<M, ?>, TableCell<M, ?>>() {
			@Override
			public TableCell<M, ?> call(TableColumn<M, ?> param) {
				return new TableCell() {
					@Override
					protected void updateItem(final Object item, boolean empty) {
						if (!empty) {
							setAlignment(Pos.CENTER);
							setText(valueHandle.getStringValue((V) item));
						} else {
							setText("");
						}
						super.updateItem(item, empty);
					}
				};
			}

		});
	}

	/**
	 * column to text field and support enter event 2014-3-29 mnt.cico
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <E extends Event, V> void buildTextFieldCol(int index, final EventType<E> eventType, final EventExecutorAndNewValueHandle<E, M, V> executor) {
		checkColWithIndex(index, ColType.TEXTFIELD);
		TableColumn table = tableView.getColumns().get(index);
		table.setCellFactory(new Callback<TableColumn<M, V>, TableCell<M, V>>() {
			@Override
			public TableCell<M, V> call(TableColumn<M, V> param) {
				return new TableCell<M, V>() {
					private EventHandler<E> eventHandler;
					private ChangeListener<Boolean> changeListener;
					@Override
					protected void updateItem(final V item, boolean empty) {
						if (tableView.getItems().size() > getIndex() && getIndex() >= 0) {
							final TextField textField;
							if(getGraphic() == null) {
								textField = new TextField(null == item ? "" : item.toString());
								textField.getStylesheets().add(getClass().getResource("css/tableColumn.css").toExternalForm());
								setAlignment(Pos.CENTER);
								setGraphic(textField);
							} else {
								textField = (TextField)getGraphic();
								textField.setText(null == item ? "" : item.toString());
							}
							if (eventType.equals(ActionEvent.ACTION)) {
								textField.setOnAction(new EventHandler<ActionEvent>() {
									@Override
									public void handle(ActionEvent event) {
										try {
											textField.setDisable(true);
											executor.execute((E)event, tableView.getItems().get(getIndex()), item, textField.getText());
										} catch (Exception e) {
											executor.onFailed(e);
										}
										textField.setDisable(false);
									}
								});
								if(null != changeListener) {
									textField.focusedProperty().removeListener(changeListener);
								}
								changeListener = new ChangeListener<Boolean>() {
									@Override
									public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
										if(!newValue) {
											try {
												textField.setDisable(true);
												executor.execute(null, tableView.getItems().get(getIndex()), item, textField.getText());
											} catch (Exception e) {
												executor.onFailed(e);
											}
											textField.setDisable(false);
										}
									}
								};
								textField.focusedProperty().addListener(changeListener);
							} else {
								if(null != eventHandler) {
									textField.removeEventHandler(eventType, eventHandler);
								}
								eventHandler = new EventHandler<E>() {
									@Override
									public void handle(E event) {
										try {
											textField.setDisable(true);
											executor.execute(event, tableView.getItems().get(getIndex()), item, textField.getText());
										} catch (Exception e) {
											executor.onFailed(e);
										}
										textField.setDisable(false);
									}
								};
								textField.addEventHandler(eventType, eventHandler);
							}
						} else {
							setGraphic(null);
						}
						super.updateItem(item, empty);
					}
				};
			}

		});
	}

	
	/**
	 * column to choiceBox and no items
	 * <p>
	 * add selected event if old value is null it is first select
	 * </p>
	 * 
	 * @since 2014-3-26 下午5:41:31 mnt.cico
	 * @param index
	 */
	public <E extends Event, V> void buildChoiceBoxCol(int index, final EventExecutorChoiceHandle<M, V> executor) {
		buildChoiceBoxCol(index, executor, null);
	}
	
	/**
	 * column to choiceBox and items
	 * <p>
	 * add selected event if old value is null it is first select
	 * </p>
	 * 
	 * @since 2014-3-26 下午5:41:31 mnt.cico
	 * @param index
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public <E extends Event, V> void buildChoiceBoxCol(int index, final EventExecutorChoiceHandle<M, V> executor, final ObservableList<V> items) {
		checkColWithIndex(index, ColType.CHOICEBOX);
		TableColumn table = tableView.getColumns().get(index);
		final ObservableList itemCb = itemMap.get(index);
		table.setCellFactory(new Callback<TableColumn<M, V>, TableCell<M, V>>() {
			@Override
			public TableCell<M, V> call(TableColumn<M, V> param) {
				return new TableCell<M, V>() {
					private InvalidationListener firstSelectListener;
					private ChangeListener<V> changeSelectListener;
					
					@Override
					protected void updateItem(final V item, boolean empty) {
						if (tableView.getItems().size() > getIndex() && getIndex() >= 0) {
							if (!empty || tableView.getItems().get(getIndex()) != null) {
								final ComboBox<V> cb;
								if(null == getGraphic()) {
									cb = new ComboBox<V>();
									cb.setPromptText("请选择");
									if(null == items) {
										cb.setItems((ObservableList<V>) itemCb);
									} else {
										cb.setItems(items);
									}
									cb.getStyleClass().add("combo-box");
									setAlignment(Pos.CENTER);
									setGraphic(cb);
								} else {
									cb = (ComboBox<V>)getGraphic();
								}

								if(null != firstSelectListener) {
									cb.getSelectionModel().selectedItemProperty().removeListener(firstSelectListener);
								}
								if(null != changeSelectListener) {
									cb.getSelectionModel().selectedItemProperty().removeListener(changeSelectListener);
								}
								firstSelectListener = new InvalidationListener() {
									@Override
									public void invalidated(Observable observable) {
										try {
											executor.execute((M) tableView.getItems().get(getIndex()), null, cb.getSelectionModel().getSelectedItem());
										} catch (Exception e) {
											executor.onFailed(e);
										}
									}
								};
								cb.getSelectionModel().selectedItemProperty().addListener(firstSelectListener);
								changeSelectListener = new ChangeListener<V>() {
									@Override
									public void changed(ObservableValue<? extends V> observable, V oldValue, V newValue) {
										try {
											cb.getSelectionModel().selectedItemProperty().removeListener(firstSelectListener);
											executor.execute((M) tableView.getItems().get(getIndex()), oldValue, newValue);
										} catch (Exception e) {
											executor.onFailed(e);
										}
									}
								};
								cb.getSelectionModel().selectedItemProperty().addListener(changeSelectListener);

								if (!empty) {
									cb.getSelectionModel().select(item);
								}
							} else {
								setGraphic(null);
							}
						} else {
							setGraphic(null);
						}
						super.updateItem(item, empty);
					}
					
				};
			}

		});
	}

	/**
	 * column to check box and execute selected event
	 * 2014-4-6 mnt.cico
	 * @param index
	 * @param executor
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	public void buildCheckBoxCol(int index, final EventExecutorCheckHandle<M> executor) {
		checkColWithIndex(index, ColType.CHECKBOX);
		TableColumn tableColumn = tableView.getColumns().get(index);
		tableColumn.setCellFactory(new Callback<TableColumn<M, ?>, TableCell<M, ?>>() {
			@Override
			public TableCell<M, ?> call(TableColumn<M, ?> param) {
				return new TableCell() {
					private ChangeListener<Boolean> changeSelectListener;
					@Override
					protected void updateItem(final Object item, boolean empty) {
						
						if (tableView.getItems().size() > getIndex() && getIndex() >= 0) {
							if(item instanceof Boolean) {
								final CheckBox checkBox;
								if(null == getGraphic()) {
									checkBox = new CheckBox();
									setAlignment(Pos.CENTER);
									setGraphic(checkBox);
								} else {
									checkBox = (CheckBox)getGraphic();
								}
								if(null != changeSelectListener) {
									checkBox.selectedProperty().removeListener(changeSelectListener);
								}
								checkBox.setSelected((boolean)item);
								changeSelectListener = new ChangeListener<Boolean>() {
									@Override
									public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
										try {
											executor.execute(newValue, (M) tableView.getItems().get(getIndex()));
										} catch (Exception e) {
											executor.onFailed(e);
										}
									}
								};
								checkBox.selectedProperty().addListener(changeSelectListener);
								
							} else {
								throw new RuntimeException("the check box must input boolean value");
							}
						} else {
							setGraphic(null);
						}
						super.updateItem(item, empty);
					}
				};
			}
		});
	}

	/**
	 * convert to date picker choice column 
	 * 2014-8-17 mnt.cico
	 * @param index
	 * @param dateHandler
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public final void buildDateChoice(int index, DatePickerHandler<M> dateHandler) {
		checkColWithIndex(index, ColType.DATEPRICKER);
		TableColumn tableColumn = tableView.getColumns().get(index);
		tableColumn.setCellFactory(param -> {
			return new TableCell() {
				@Override
				protected void updateItem(final Object item, boolean empty) {
					if (tableView.getItems().size() > getIndex() && getIndex() >= 0) {
						final DatePicker dp;
						if(null == getGraphic()) {
							dp = new DatePicker();
							dp.getStyleClass().add("date-picker");
							setAlignment(Pos.CENTER);
							setGraphic(dp);
						} else {
							dp = (DatePicker)getGraphic();
						}
						if(item instanceof Date) {
							dp.setValue(DateConvertFactory.getLocalDateForDate((Date)item));
						} else if(item instanceof LocalDate){
							dp.setValue((LocalDate)item);
						}
						dp.valueProperty().addListener((obs, olderValue, newValue) -> {
							dateHandler.choiceDate(newValue, (M) tableView.getItems().get(getIndex()));
						});
					} else {
						setText("");
						setGraphic(null);
					}
					super.updateItem(item, empty);
				}
			};
		});
	}
	
	public final void buildMenu() {
		Platform.runLater(new Runnable() {
			@Override
			public void run() {
				ContextMenu menu = new ContextMenu();
				tableView.setContextMenu(menu);
				tableView.getColumns().forEach(cloumn -> {
					MenuItem item = new MenuItem();
					final CheckBox cb = new CheckBox(cloumn.getText());
					item.setGraphic(cb);
					cb.setSelected(true);
					cb.setOnAction(new EventHandler<ActionEvent>(){
						@Override
						public void handle(ActionEvent event) {
							cloumn.setVisible(cb.isSelected());
						}
					});
					menu.getItems().add(item);
				});
			}
		});
		
	}
	
	/**
	 * build filter by the table
	 * 2014-7-27 mnt.cico
	 * @param filter
	 */
	public final void buildFilter(Predicate<M> filter) {
		final ObservableList<M> itemTemp = FXCollections.observableArrayList();
		itemTemp.clear();
		tableItem.stream().filter(filter).forEach(item -> {
			itemTemp.add(item);
		});
		tableView.setItems(itemTemp);
	}
	
	/**
	 * clear the filter
	 * 20147-27 mnt.cico
	 */
	public final void clearFilter() {
		tableView.setItems(tableItem);
	}
	
	/**
	 * 
	 * <p>
	 * check the column is equal annotation set column type
	 * </p>
	 * 
	 * @since 2014年3月26日 下午4:39:18 Cico.Jiang
	 * @param index
	 * @param colType
	 */
	private void checkColWithIndex(int index, ColType colType) {
		if (null == colTypeValidateMap.get(index)) {
			throw new RuntimeException("type :" + colType + "not equal  : " + colTypeValidateMap.get(index));
		}
	}
	
	
	public static final <M>TableData getTableData(TableView<M> tableView) {
		final TableData result = new TableData();
		final ObservableList<M> items = tableView.getItems();
		ObservableList<TableColumn<M, ?>> columns = tableView.getColumns();
		ObservableList<TableColumn<M, ?>> visibleColumns = FXCollections.observableArrayList();
		
		for (TableColumn<M, ?> tableColumn : columns) {
			if(tableColumn.isVisible()) {
				visibleColumns.add(tableColumn);
			}
		}
		
		List<Pair<String, Double>> titleList = new ArrayList<>();
		List<String[]> contentList = new ArrayList<>();
		Pair<String, Double> title;
		
		for (TableColumn<M, ?> tableColumn : visibleColumns) {
			if(tableColumn.isVisible()) {
				title = new Pair<String, Double>(tableColumn.getText(), tableColumn.getWidth());
				titleList.add(title);
			}
		}
		
		String[] columnText;
		Object context;
		for (M model : items) {
			columnText = new String[visibleColumns.size()];
			for (int i = 0; i < visibleColumns.size(); i++) {
				context = visibleColumns.get(i).getCellData(model);
				if(context != null) {
					columnText[i] = context.toString();
				} else {
					columnText[i] = "";
				}
			}
			contentList.add(columnText);
		}
		result.setContentList(contentList);
		result.setTitleList(titleList);
		return result;
	}
}
